/**
 * CLUSTRophobia is a solver for the clustering problem, it is
 * written for beign able to use several meta-heuristics for
 * solving the problem and also different metrics for meassuring
 * the intra/inter cluster distance.
 * 
 * @author Carlos Colmenares (06-39380)
 * @author Kelwin Fernández (07-40888)
 */

#include <cstdio>
#include <cstring>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "meta_heuristics.h"
#include "utils.h"

using namespace std;

void print_usage();

global_data g_data;

int main(int argc, char** argv){


    //These are the global variables with their default
    //values
    g_data.program_name = argv[0];
    g_data.data_input_file = NULL;
    g_data.output_file = NULL;
    g_data.program_name = NULL;
    g_data.num_clusters = -1;
    g_data.max_iterations = 1000;
    g_data.meta_heuristic = (char*)"LS";
    g_data.criterion = (char*)"Davies-Bouldin";
    g_data.problem_instance = NULL;
    
    g_data.num_ants = 20;
    g_data.aca_q = 0.7;
    
    g_data.population_size = 100;
    g_data.num_couples = 5;
    g_data.mutation_prob = 0.3;
    g_data.prop_restart = 0.3;
    g_data.tournament_size = 5;

    srand ( time(NULL) );
    
    {
        int i=1;
        while( i<argc ){
            
            if( strcmp(argv[i],"-f") == 0 && i+1<argc ){
                
                g_data.data_input_file = argv[++i];
                
            } else if ( strcmp(argv[i], "-n") == 0 && i+1<argc){
                
                g_data.num_clusters = atoi(argv[++i]);
            
            } else if ( strcmp(argv[i], "-i") == 0 && i+1<argc){
                
                g_data.max_iterations = atoi(argv[++i]);
            
            } else if ( strcmp(argv[i], "-h") == 0 && i+1<argc){
                
                g_data.meta_heuristic= argv[++i];
            
            } else if ( strcmp(argv[i], "-c") == 0 && i+1<argc){
                
                g_data.criterion = argv[++i];
            
            } else if ( strcmp(argv[i], "-o") == 0 && i+1<argc){
                
                g_data.output_file = argv[++i];
            
            } else if ( strcmp(argv[i], "-an") == 0 && i+1<argc){
                
                g_data.num_ants = atoi(argv[++i]);
                
            } else if ( strcmp(argv[i], "-aq") == 0 && i+1<argc){
                
                g_data.aca_q = atof(argv[++i]);
                
            } else if ( strcmp(argv[i], "-pop") == 0 && i+1<argc){
                
                g_data.population_size = atoi(argv[++i]);
                
            } else if ( strcmp(argv[i], "-nc") == 0 && i+1<argc){
                
                g_data.num_couples = atoi(argv[++i]);
                
            } else if ( strcmp(argv[i], "-mut") == 0 && i+1<argc){
                
                g_data.mutation_prob = atof(argv[++i]);
                
            } else if ( strcmp(argv[i], "-r") == 0 && i+1<argc){
                    
                g_data.prop_restart = atof(argv[++i]);
                
            } else if ( strcmp(argv[i], "-t") == 0 && i+1<argc){
                
                g_data.tournament_size = atoi(argv[++i]);
                
            } else {
                print_usage();                
                exit(1);
            }
            
            i++;
        }

    }
    
    //Check the command line arguments for correctness
    if( g_data.data_input_file == NULL ){
        fprintf(stderr,"An input file must be specified\n");
        print_usage();
        exit(1);
    }
    if( g_data.num_clusters <= 1 
        && !(    strcmp(g_data.meta_heuristic,"ACA") == 0
              || strcmp(g_data.meta_heuristic,"ACHA") == 0
            )
    ){
        fprintf(stderr,"A valid number of clusters must be specified ( more than 1 )\n");
        print_usage();
        exit(1);
    }
    if( g_data.max_iterations <= 0 ){
        fprintf(stderr,"Invalid number of max iterations. Only positive integers are admitted\n");
        print_usage();
        exit(1);
    }
    if(!(
        strcmp(g_data.criterion,"Davies-Bouldin") == 0 ||
        strcmp(g_data.criterion,"Dunn") == 0
      ))
    {
        fprintf(stderr,"Invalid criterion of quality, please indicate an available one\n");
        print_usage();
        exit(1);
    }
    if (!g_data.num_ants > 0 ){
        fprintf(stderr,"A valid number of ants must be specified ( more than 0 )\n");
        print_usage();
        exit(1);
    }
    if (!g_data.aca_q > 0 ){
        fprintf(stderr,"A valid probability must be specified [0,1]\n");
        print_usage();
        exit(1);
    }
    
    //Solve the problem and get the final state
    state* solution;
    g_data.initialize();
    g_data.problem_instance->normalize();
    
    //Use the metha-heuristic that was specified
    if( strcmp(g_data.meta_heuristic,"LS") == 0 ) {
        
        solution = local_search();
    
    } else if( strcmp(g_data.meta_heuristic,"ILS") == 0 ){
    
        solution = iterated_local_search();

    } else if(strcmp(g_data.meta_heuristic,"TS") == 0 ){
    
        solution = taboo_search();

    } else if(strcmp(g_data.meta_heuristic,"VNS") == 0 ){
        
        solution = variable_neighborhood_search();
        
    } else if(strcmp(g_data.meta_heuristic,"KM") == 0 ){
        
        solution = k_means();

    } else if(strcmp(g_data.meta_heuristic,"SA") == 0 ){
        
        solution = simulated_annealing();
    
    } else if(strcmp(g_data.meta_heuristic,"ACA") == 0 ){
        
        solution = ant_clust_algorithm();
        
    } else if(strcmp(g_data.meta_heuristic,"ACHA") == 0 ){
        
        solution = ant_clust_hybrid_algorithm();
        
    } else if(strcmp(g_data.meta_heuristic,"MACA") == 0 ){
        
        solution = meta_ant_clust_algorithm();
        
    } else if(strcmp(g_data.meta_heuristic,"GA") == 0 ){
        
        solution = genetic_algorithm();
        
    } else if(strcmp(g_data.meta_heuristic,"HGA") == 0 ){
        
        solution = hybrid_genetic_algorithm();
        
    } else if(strcmp(g_data.meta_heuristic,"AMGA") == 0 ){
        
        solution = alpha_male_genetic_algorithm();
        
    } else {
        fprintf(stderr,"Invalid or non-existent meta-heuristic\n");
        print_usage();
        exit(1);
    }

    //Refine the solution using the k-means algorithm
    //solution = k_means(solution);
    
    //Report the solution
    cout << "Solution found: " << endl;    
    cout << g_data.meta_heuristic << ": ";
    solution->print();
    
    //for (int d=0; d < solution->prob->n_data; d++){
    //    double min_dist = -1.0;
    //    int min_cluster = -1;
    //    
    //    // Compute the distance of each observation to its centroid.
    //    for (int c=0; c<solution->num_clusters; c++){
    //        double dist = 
    //          euclidean_distance(solution->prob->n_attributes, solution->prob->data[d],
    //                             solution->centroids[c]);
    //        
    //        if ( min_cluster == -1 || dist < min_dist ){
    //            min_cluster = c;
    //            min_dist = dist;
    //        }
    //    }
    //    cout << "(" << d+1 << ": " << min_cluster << ") ";
    //}

    //Print the solution found if an output file was
    //specified
    if( g_data.output_file != NULL ){
        ofstream ofs;
        ofs.open(g_data.output_file);
        
        ofs << solution->value << " " << g_data.num_iterations << endl
            << solution->num_clusters << endl;
        
        g_data.problem_instance->un_normalize();
        
        for(int i=0; i<solution->num_clusters; i++){
            g_data.problem_instance->un_normalize(solution->centroids[i]);
            for(int j=0;j<solution->prob->n_attributes;j++){
                if(j!=0){
                    ofs << ",";
                }
                ofs << solution->centroids[i][j];
            }
            ofs << endl;
        }
        
        solution->assign_nearest();
        
        //For each point in data, print the cluster it belongs to
        for(int i=0;i<solution->prob->n_data;i++){
            ofs << solution->closest_centroid[i] << endl;
        }

        ofs.close();
    }
    
    return 0;
}

void print_usage(){

    printf("usage: %s -f <data_file> [OPTIONS]\n",g_data.program_name);
    printf("\n");
    printf("    OPTIONS:\n");
    printf("---------------------------------------------------------------\n");
    printf("\n");
    printf("    GENERAL OPTIONS:\n");
    printf("\n");
    printf("    -n <num_clusters> Necessary in case of a non-constructive\n");
    printf("                      metaheuristic\n");
    printf("\n");
    printf("    --help            Print this message\n");
    printf("\n");
    printf("    -i <max_it>       Maximum iterations for the algorithm\n");
    printf("                      Default value: 1000\n");
    printf("\n");
    printf("    -h <meta-heur>    Meta-heuristic to be used\n");
    printf("                        LS:    Local Search\n");
    printf("                        ILS:   Iterated Local Search\n");
    printf("                        TS:    Tabu Search\n");
    printf("                        VNS:   Variable Neighborhood Search\n");
    printf("                        KM:    K-Means\n");
    printf("                        SA:    Simulated Annealing\n");
    printf("                        ACA:   AntClust Algorithm\n");
    printf("                        ACHA:  AntClust Hybrid Algorithm\n");
    printf("                        GA:    Genetic Algorithm\n");
    printf("                        HGA:   Hybrid Genetic Algorithm\n");
    printf("                        AMGA:  Alpha-Male Genetic Algorithm\n");
    printf("                      Default value: LS\n");
    printf("\n");
    printf("    -o <output_file>\n");
    printf("\n");
    printf("    -c <criterion>    Internal criterion to be used\n");
    printf("                        Davies-Bouldin\n");
    printf("                        Dunn\n");
    printf("                      Default value: Davies-Bouldins\n");
    printf("\n\n");
    printf("---------------------------------------------------------------\n");
    printf("\n");
    printf("    ANTCLUST OPTIONS:\n");
    printf("\n");
    printf("    -an <ants_num>    Number of ants in a colony.\n");
    printf("                      Valid with ACA or ACHA\n");
    printf("                      Default value: 20\n");
    printf("\n");
    printf("    -aq <ants_q>      Probability of pick a element from a 2-cluster\n");
    printf("                      Valid with ants metaheuristic (ACA and ACHA)\n");
    printf("                      Default value: 0.7\n");
    printf("\n\n");
    printf("---------------------------------------------------------------\n");
    printf("\n");
    printf("    GENETIC ALGORITHM OPTIONS:\n");
    printf("\n");
    printf("    -pop <pop_size>   Number of individuals in a GA's population\n");
    printf("                      Default value: 100\n");
    printf("\n");
    printf("    -nc <num_couples> Number of couples to crossover\n");
    printf("                      Default value: 5\n");
    printf("\n");
    printf("    -mut <prob>       Probability of mutate a offspring\n");
    printf("                      Default value: 0.3\n");
    printf("\n");
    printf("    -r <prob>         Proportion of generations without changes\n");
    printf("                      to replace the population\n");
    printf("                      Default value: 0.3\n");
    printf("\n");
    printf("    -t <size>         Size of a tournament to replace the\n");
    printf("                      alpha-male in a AMGA\n");
    printf("                      Default value: 5\n");
    printf("\n");
}

